package se.thoka.twt.client.json;

import static se.thoka.twt.client.json.JSONFieldParserUtil.isType;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import se.thoka.twt.client.utils.TWTLogLevel;
import se.thoka.twt.client.utils.TWTLogger;

import com.google.gwt.json.client.JSONValue;
import com.gwtent.reflection.client.Field;
import com.gwtent.reflection.client.Type;

@SuppressWarnings("rawtypes")
public class JSONArrayParser implements JSONFieldParserUtil.Parser {
	
	private final List<CollectionPair<?>> pairs;
	
	public JSONArrayParser() {
		pairs = new ArrayList<CollectionPair<?>>();
		pairs.add(new CollectionPair<ArrayList>(ArrayList.class, Collection.class, List.class) {
			@Override
			protected ArrayList createImpl() {
				return new ArrayList();
			}
		});
	}

	@Override
	public boolean accept(Field field) {
		boolean result = false;
		if (getCollectionPair(field) != null) {
			JsonArray annotation = field.getAnnotation(JsonArray.class);
			if (annotation == null) {
				TWTLogger.log(TWTLogLevel.ERROR, "Field " + field + " is a collection and should be annotated with " + JsonArray.class);
			} else {
				result = true;
			}
		}
		return result;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Object parse(Field field, JSONValue value) {
		JsonArray annotation = field.getAnnotation(JsonArray.class);
		Class<?> objectType = annotation.type();
		CollectionPair<?> collectionPair = getCollectionPair(field);
		Collection parsedCollection = TWTJSONParser.parseArray(objectType, value);
		Collection result = null;
		if (parsedCollection != null) {
			result = collectionPair.createImpl();
			for (Object o : parsedCollection) {
				result.add(o);
			}
		}
		return result;
	}
	
	private CollectionPair<?> getCollectionPair(Field field) {
		CollectionPair<?> result = null;
		for (CollectionPair<?> pair : pairs) {
			if (pair.accept(field)) {
				result = pair;
				break;
			}
		}
		return result;
	}
	
	private abstract static class CollectionPair<T extends Collection<?>> {
		private final Class<T> implClass;
		private final Class<?>[] abstractClasses;
		
		public CollectionPair(Class<T> implClass, Class<?>... abstractClasses) {
			this.implClass = implClass;
			this.abstractClasses = abstractClasses;
		}
		
		public boolean accept(Field field) {
			boolean result = false;
			Type type = field.getType();
			if (isType(type, implClass)) {
				result = true;
			} else {
				for (Class<?> abstractClass : abstractClasses) {
					if (isType(type, abstractClass)) {
						result = true;
						break;
					}
				}
			}
			
			return result;
		}
		
		protected abstract T createImpl();
	}

}
